/**
 * 
 */
package Verifier.ArithmeticSystem;

import java.math.BigInteger;

import Verifier.Cryptography.PRG;

/**
 * This class represents the group which is a product of other groups. It can represents G_q^6 and even G_q x (G_q^2), and any other combination.
 * 
 * @author Moshe Baavur
 *
 */
public class ProductGroup extends Group {
	// the list of groups which create the product
	Group[] groupsList;
	// represents this modular group's order
	private BigInteger groupOrder;
	// represents the containing group's order
	private BigInteger containerGroupOrder;
	// the encoding scheme of the group
	private GroupEncodingScheme encodingScheme;
	// the group's identity element
	private ProductGroupElement identityElement;
	// the group's creator element
	private ProductGroupElement creatorElement;
	
	/**
	 * Creates a product group which is a sequence of groups that are representing the types of elements in each product element that belongs to this group.
	 * 
	 * @param groupsList the sequence of the groups in the order they appear in the product group
	 * @throws Exception if the groupList is an empty array
	 */
	public ProductGroup(Group[] groupsList) throws Exception {
		if (0 == groupsList.length) {
			// the product can't be empty
			throw new Exception("The product should contain at least one group");
		}
		
		this.groupsList = groupsList;
		// calculate the order of the group and of the container group - this order can't
		// be a prime number because it is a multiplication of orders
		groupOrder = BigInteger.ONE;
		containerGroupOrder = BigInteger.ONE;
		creatorElement = new ProductGroupElement(this);
		identityElement = new ProductGroupElement(this);
		for (int i = 0; i < groupsList.length; i++) {
			// multiply the order of the product group by the order of the iterated group's order
			groupOrder = groupOrder.multiply(groupsList[i].getOrder());
			// multiply the order of the container product group by the order of the iterated group's container order
			containerGroupOrder = containerGroupOrder.multiply(groupsList[i].getContainingGroupOrder());
			// add the iterated group's creator to the creator of the product group
			creatorElement.addGroupElement(groupsList[i].getGroupCreatorAsGroupElement());
			// add the iterated group's identity to the identity of the product group
			identityElement.addGroupElement(groupsList[i].getGroupIdentityElement());
		}
		// no encoding scheme to a product group
		encodingScheme = GroupEncodingScheme.ENCODING_SCHEME_UNKNOWN;
	}
	
	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#getOrder()
	 */
	@Override
	public BigInteger getOrder() {
		return (this.groupOrder);
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#getContainingGroupOrder()
	 */
	@Override
	public BigInteger getContainingGroupOrder() {
		return (this.containerGroupOrder);
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#getGroupCreator()
	 */
	@Override
	public BigInteger getGroupCreator() {
		return (null);
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#getGroupCreatorAsGroupElement()
	 */
	@Override
	public GroupElement getGroupCreatorAsGroupElement() {
		return (this.creatorElement);
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#getEncodingScheme()
	 */
	@Override
	public int getEncodingScheme() {
		// in this type of group, the encoding scheme isn't defined
		return encodingScheme.getValue();
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#getGroupIdentityElement()
	 */
	@Override
	public GroupElement getGroupIdentityElement() {
		return (this.identityElement);
	}
	
	/**
	 * Returns the size of the product.
	 * 
	 * @return the size of the product.
	 */
	public int getProductSize() {
		return (groupsList.length);
	}
	
	/**
	 * Returns the group at [index] index in the product.
	 * 
	 * @param index the index of the group to return.
	 * @return the group at index [index].
	 */
	public Group getGroupInProduct(int index) {
		return (groupsList[index]);
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#doesElementBelong(Verifier.ArithmeticSystem.GroupElement)
	 */
	@Override
	public boolean doesElementBelong(GroupElement element) {
		// check if the element belongs to the product group
		
		// check that the element is a product group element
		if (!(element instanceof ProductGroupElement))
			return (false);
		
		ProductGroupElement productElement = (ProductGroupElement)element;
		// check that the size of the product equals to the size of the product group
		if (groupsList.length != productElement.getNumberOfElements())
			return (false);
		
		// now, check that each component in the product is belongs to the appropriate group in the product list
		for (int i = 0; i < productElement.getNumberOfElements(); i++) {
			if (false == groupsList[i].doesElementBelong(productElement.getElement(i))) {
				// the element doesn't belong to the product - the entire group doesn't belong
				return (false);
			}
		}
		// the element should be in the group
		return (true);
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#encode(byte[])
	 */
	@Override
	public GroupElement encode(byte[] message) throws Exception {
		// there is no encoding to a product group at the moment
		return null;
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#decode(Verifier.ArithmeticSystem.GroupElement)
	 */
	@Override
	public byte[] decode(GroupElement message) {
		// there is no decoding to a product group at the moment
		return null;
	}

	/* (non-Javadoc)
	 * @see Verifier.ArithmeticSystem.Group#randomArray(int, Verifier.Cryptography.PRG, byte[], int)
	 */
	@Override
	public GroupElement[] randomArray(int arraySize, PRG pseudoGenerator,
			byte[] seed, int error) {
		// there is no random array to a product group at the moment
		return null;
	}

	@Override
	public boolean equals(Object obj) {
		// if the object is a product group, then compare them using their groups and not the address
		if (obj instanceof ProductGroup) {
			ProductGroup objGroup = (ProductGroup)obj;
			// check that their groups lists are of the same size
			if (objGroup.groupsList.length != groupsList.length)
				return (false);
			// check each group in the products for equality
			for (int i = 0; i < groupsList.length; i++) {
				if (!groupsList[i].equals(objGroup.groupsList[i]))
					return (false);
			}
			
			return (true);
		}
		// otherwise, they are not equal
		return (false);
	}
}
